package com.xhj.mybatis.scripting.xmltags;

import com.xhj.mybatis.builder.SqlSourceBuilder;
import com.xhj.mybatis.mapping.BoundSql;
import com.xhj.mybatis.mapping.SqlSource;
import com.xhj.mybatis.session.Configuration;

import java.sql.PreparedStatement;

/**
 * DynamicSqlSource
 *
 * @author XJks
 * @description DynamicSqlSource 类实现了 SqlSource 接口，用于处理动态 SQL 语句
 */
public class DynamicSqlSource implements SqlSource {

    /**
     * 保存 MyBatis 的全局配置对象
     */
    private final Configuration configuration;

    /**
     * 保存 SQL 节点的根节点，代表整个 SQL 的结构
     */
    private final SqlNode rootSqlNode;

    /**
     * 构造方法，初始化配置对象和根 SQL 节点
     *
     * @param configuration MyBatis 配置对象
     * @param rootSqlNode   SQL 节点树的根节点
     */
    public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
    }

    /**
     * 根据传入的参数对象，生成包含 SQL 语句和参数信息的 BoundSql 对象。
     *
     * @param parameterObject 用户传入的参数对象，可能用于动态生成 SQL
     * @return BoundSql 返回包含最终 SQL 语句和参数映射的对象
     */
    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        // 创建动态上下文对象，保存参数和 SQL 拼接过程中的变量
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        // TODO:使用根 SQL 节点生成最终的 SQL 语句，将结果存放到 context 上下文中
        rootSqlNode.apply(context);
        // 创建 SQL 解析器，用于将动态 SQL 转换为静态 SQL
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        // 获取参数类型，如果参数为空则用 Object.class
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        // 解析上下文中的 SQL，生成 SqlSource 对象
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        // 获取最终的 BoundSql 对象，包含 SQL 和参数
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        /**
         * TODO:
         *  1.将上下文中的所有绑定变量设置到 BoundSql 的附加参数中，这样做是因为动态 SQL 可能会生成额外的参数，后续在执行 SQL 时需要使用这些参数。
         *  2.在 {@link com.xhj.mybatis.scripting.defaults.DefaultParameterHandler#setParameters(PreparedStatement)} 方法中会使用这些附加参数，将对应的值设置到 PreparedStatement 中。
         */
        context.getBindings().forEach(boundSql::setAdditionalParameter);
        // 返回最终的 BoundSql 对象
        return boundSql;
    }

}
